﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Xml;
using System.ComponentModel.Design;
using System.Collections;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Globalization;


namespace CXMLStudioIDE.DesignerHost
{
    public class FormSerializer
    {
        private static readonly Attribute[] propertyAttributes = new Attribute[] {
			DesignOnlyAttribute.No
		};
        private IDesignerHost host;

        public FormSerializer(IDesignerHost _host)
        {
            this.host = _host;
        }
        #region Serialize - Flush
        /// <summary>
        /// This will recussively go through all the objects in the tree and
        /// serialize them to Xml
        /// </summary>
        public XmlDocument PerformFlushWorker()
        {
            IComponent root;
            XmlDocument document = new XmlDocument();
            document.AppendChild(document.CreateElement("DOCUMENT_ELEMENT"));

            IDesignerHost idh = (IDesignerHost)this.host.GetService(typeof(IDesignerHost));
            root = idh.RootComponent;

            Hashtable nametable = new Hashtable(idh.Container.Components.Count);
            IDesignerSerializationManager manager = host.GetService(typeof(IDesignerSerializationManager)) as IDesignerSerializationManager;

            document.DocumentElement.AppendChild(WriteObject(document, nametable, root));
            foreach (IComponent comp in idh.Container.Components)
            {
                if (comp != root && !nametable.ContainsKey(comp))
                {
                    document.DocumentElement.AppendChild(WriteObject(document, nametable, comp));
                }
            }

            return document;
        }

        private XmlNode WriteObject(XmlDocument document, IDictionary nametable, object value)
        {
            IDesignerHost idh = (IDesignerHost)this.host.GetService(typeof(IDesignerHost));
            Debug.Assert(value != null, "Should not invoke WriteObject with a null value");

            XmlNode node = document.CreateElement("Object");
            XmlAttribute typeAttr = document.CreateAttribute("type");

            typeAttr.Value = value.GetType().AssemblyQualifiedName;
            node.Attributes.Append(typeAttr);

            IComponent component = value as IComponent;

            if (component != null && component.Site != null && component.Site.Name != null)
            {
                XmlAttribute nameAttr = document.CreateAttribute("name");
                nameAttr.Value = component.Site.Name;
                node.Attributes.Append(nameAttr);
                //Starts APS customized code
                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(component, propertyAttributes);
                PropertyDescriptor controlProp = properties["Text"];
                if (controlProp != null)
                {
                    PropertyDescriptor[] propArray = new PropertyDescriptor[1];
                    propArray[0] = controlProp;
                    properties = new PropertyDescriptorCollection(propArray);
                    WriteProperties(document, properties, value, node, "Property");
                }
                //End customized code
                Debug.Assert(nametable[component] == null, "WriteObject should not be called more than once for the same object.  Use WriteReference instead");
                nametable[value] = component.Site.Name;
            }

            bool isControl = (value is Control);

            if (isControl)
            {
                XmlAttribute childAttr = document.CreateAttribute("children");

                childAttr.Value = "Controls";
                node.Attributes.Append(childAttr);
            }

            if (component != null)
            {
                if (isControl)
                {
                    foreach (Control child in ((Control)value).Controls)
                    {
                        if (child.Site != null && child.Site.Container == idh.Container)
                        {
                            node.AppendChild(WriteObject(document, nametable, child));
                        }
                    }
                }// if isControl

                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value, propertyAttributes);

                if (isControl)
                {
                    PropertyDescriptor controlProp = properties["Controls"];

                    if (controlProp != null)
                    {
                        PropertyDescriptor[] propArray = new PropertyDescriptor[properties.Count - 1];
                        int idx = 0;

                        foreach (PropertyDescriptor p in properties)
                        {
                            if (p != controlProp)
                            {
                                propArray[idx++] = p;
                            }
                        }

                        properties = new PropertyDescriptorCollection(propArray);
                    }
                }

                WriteProperties(document, properties, value, node, "Property");

                EventDescriptorCollection events = TypeDescriptor.GetEvents(value, propertyAttributes);
                IEventBindingService bindings = host.GetService(typeof(IEventBindingService)) as IEventBindingService;

                if (bindings != null)
                {
                    properties = bindings.GetEventProperties(events);
                    WriteProperties(document, properties, value, node, "Event");
                }
            }
            else
            {
                WriteValue(document, value, node);
            }

            return node;
        }
        private void WriteProperties(XmlDocument document, PropertyDescriptorCollection properties, object value, XmlNode parent, string elementName)
        {
            foreach (PropertyDescriptor prop in properties)
            {
                if (prop.Name == "AutoScaleBaseSize")
                {
                    string _DEBUG_ = prop.Name;
                }

                if (prop.ShouldSerializeValue(value))
                {
                    string compName = parent.Name;
                    XmlNode node = document.CreateElement(elementName);
                    XmlAttribute attr = document.CreateAttribute("name");

                    attr.Value = prop.Name;
                    node.Attributes.Append(attr);

                    DesignerSerializationVisibilityAttribute visibility = (DesignerSerializationVisibilityAttribute)prop.Attributes[typeof(DesignerSerializationVisibilityAttribute)];

                    switch (visibility.Visibility)
                    {
                        case DesignerSerializationVisibility.Visible:
                            if (!prop.IsReadOnly && WriteValue(document, prop.GetValue(value), node))
                            {
                                parent.AppendChild(node);
                            }

                            break;

                        case DesignerSerializationVisibility.Content:
                            object propValue = prop.GetValue(value);

                            if (typeof(IList).IsAssignableFrom(prop.PropertyType))
                            {
                                WriteCollection(document, (IList)propValue, node);
                            }
                            else
                            {
                                PropertyDescriptorCollection props = TypeDescriptor.GetProperties(propValue, propertyAttributes);

                                WriteProperties(document, props, propValue, node, elementName);
                            }

                            if (node.ChildNodes.Count > 0)
                            {
                                parent.AppendChild(node);
                            }

                            break;

                        default:
                            break;
                    }
                }
            }
        }
        private XmlNode WriteReference(XmlDocument document, IComponent value)
        {
            IDesignerHost idh = (IDesignerHost)this.host.GetService(typeof(IDesignerHost));

            Debug.Assert(value != null && value.Site != null && value.Site.Container == idh.Container, "Invalid component passed to WriteReference");

            XmlNode node = document.CreateElement("Reference");
            XmlAttribute attr = document.CreateAttribute("name");

            attr.Value = value.Site.Name;
            node.Attributes.Append(attr);
            return node;
        }
        private bool GetConversionSupported(TypeConverter converter, Type conversionType)
        {
            return (converter.CanConvertFrom(conversionType) && converter.CanConvertTo(conversionType));
        }
        private bool WriteValue(XmlDocument document, object value, XmlNode parent)
        {
            IDesignerHost idh = (IDesignerHost)this.host.GetService(typeof(IDesignerHost));

            // For empty values, we just return.  This creates an empty node.
            if (value == null)
            {
                return true;
            }

            TypeConverter converter = TypeDescriptor.GetConverter(value);

            if (GetConversionSupported(converter, typeof(string)))
            {
                parent.InnerText = (string)converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(string));
            }
            else if (GetConversionSupported(converter, typeof(byte[])))
            {
                byte[] data = (byte[])converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(byte[]));

                parent.AppendChild(WriteBinary(document, data));
            }
            else if (GetConversionSupported(converter, typeof(InstanceDescriptor)))
            {
                InstanceDescriptor id = (InstanceDescriptor)converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(InstanceDescriptor));

                parent.AppendChild(WriteInstanceDescriptor(document, id, value));
            }
            else if (value is IComponent && ((IComponent)value).Site != null && ((IComponent)value).Site.Container == idh.Container)
            {
                parent.AppendChild(WriteReference(document, (IComponent)value));
            }
            else if (value.GetType().IsSerializable)
            {
                BinaryFormatter formatter = new BinaryFormatter();
                MemoryStream stream = new MemoryStream();

                formatter.Serialize(stream, value);

                XmlNode binaryNode = WriteBinary(document, stream.ToArray());

                parent.AppendChild(binaryNode);
            }
            else
            {
                return false;
            }

            return true;
        }

        private void WriteCollection(XmlDocument document, IList list, XmlNode parent)
        {
            foreach (object obj in list)
            {
                XmlNode node = document.CreateElement("Item");
                XmlAttribute typeAttr = document.CreateAttribute("type");

                typeAttr.Value = obj.GetType().AssemblyQualifiedName;
                node.Attributes.Append(typeAttr);
                WriteValue(document, obj, node);
                parent.AppendChild(node);
            }
        }
        private XmlNode WriteBinary(XmlDocument document, byte[] value)
        {
            XmlNode node = document.CreateElement("Binary");

            node.InnerText = Convert.ToBase64String(value);
            return node;
        }
        private XmlNode WriteInstanceDescriptor(XmlDocument document, InstanceDescriptor desc, object value)
        {
            XmlNode node = document.CreateElement("InstanceDescriptor");
            BinaryFormatter formatter = new BinaryFormatter();
            MemoryStream stream = new MemoryStream();

            formatter.Serialize(stream, desc.MemberInfo);

            XmlAttribute memberAttr = document.CreateAttribute("member");

            memberAttr.Value = Convert.ToBase64String(stream.ToArray());
            node.Attributes.Append(memberAttr);
            foreach (object arg in desc.Arguments)
            {
                XmlNode argNode = document.CreateElement("Argument");

                if (WriteValue(document, arg, argNode))
                {
                    node.AppendChild(argNode);
                }
            }

            if (!desc.IsComplete)
            {
                PropertyDescriptorCollection props = TypeDescriptor.GetProperties(value, propertyAttributes);

                WriteProperties(document, props, value, node, "Property");
            }

            return node;
        }

        #endregion
    }
}
